#pragma once
#ifndef COMMON_SHADER_HPP_
#define COMMON_SHADER_HPP_

#include "glad/glad.h"
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader {
public:
	Shader(const char* vertex_path, const char* fragment_path) {
		std::string vertex_code;
		std::string fragment_code;
		std::ifstream v_shader_file;
		std::ifstream f_shader_file;

		v_shader_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
		f_shader_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);

		try {
			v_shader_file.open(vertex_path);
			f_shader_file.open(fragment_path);
			std::stringstream v_shader_stream, f_shader_stream;
			v_shader_stream << v_shader_file.rdbuf();
			f_shader_stream << f_shader_file.rdbuf();
			v_shader_file.close();
			f_shader_file.close();

			vertex_code = v_shader_stream.str();
			fragment_code = f_shader_stream.str();
		}
		catch (const std::exception& e) {
			std::cerr << "ERROR, SHADER: " << e.what() << std::endl;
		}

		uint32_t vertex_shader = compile_shader(1, vertex_code.c_str(), GL_VERTEX_SHADER);
		uint32_t fragment_shader = compile_shader(1, fragment_code.c_str(), GL_FRAGMENT_SHADER);

		id_ = glCreateProgram();
		glAttachShader(id_, vertex_shader);
		glAttachShader(id_, fragment_shader);
		glLinkProgram(id_);
		int32_t success;
		char log[512];
		glGetProgramiv(id_, GL_LINK_STATUS, &success);
		if (!success) {
			glGetProgramInfoLog(id_, 512, nullptr, log);
			std::cout << "LINK SHADER ERROR: " << log << std::endl;
		}
		glDeleteShader(vertex_shader);
		glDeleteShader(fragment_shader);
	}

	uint32_t compile_shader(int32_t num, const char* shader_source, int32_t shader_type) {
		uint32_t shader_handle = glCreateShader(shader_type);
		glShaderSource(shader_handle, 1, &shader_source, nullptr);
		glCompileShader(shader_handle);

		int32_t success;
		char log[512];
		glGetShaderiv(shader_handle, GL_COMPILE_STATUS, &success);
		if (!success) {
			glGetShaderInfoLog(shader_handle, 512, nullptr, log);
			std::cout << "COMPILE SHADER ERROR: " << log << " for shader source: " << shader_source << std::endl;
		}

		return shader_handle;
	}

	void use() { glUseProgram(id_); }

	// utility uniform function
	void set_bool(const std::string& name, bool value) {
		glUniform1i(glGetUniformLocation(id_, name.c_str()), static_cast<int>(value));
	}
	void set_int(const std::string& name, int value) {
		glUniform1i(glGetUniformLocation(id_, name.c_str()), value);
	}
	void set_float(const std::string& name, float value) {
		glUniform1f(glGetUniformLocation(id_, name.c_str()), value);
	}

private:
	uint32_t id_;
};

#endif  // COMMON_SHADER_HPP_
